package com.rw.tool.util.expression.expression;

import com.rw.tool.util.CollectionUtil;
import com.rw.tool.util.expression.element.Calculation;
import com.rw.tool.util.expression.element.ExpressionElement;
import com.rw.tool.util.expression.element.LeftParentheses;
import com.rw.tool.util.expression.element.NumberElement;
import com.rw.tool.util.expression.element.Parentheses;
import com.rw.tool.util.expression.element.RightParentheses;
import com.rw.tool.util.expression.element.Sign;
import com.rw.tool.util.expression.exception.PostfixPrefixExpressionException;
import com.rw.tool.util.expression.exception.SignException;
import com.rw.tool.util.expression.expression.tree.ElementNode;
import com.rw.tool.util.string.StringFormatter;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import java.util.stream.Collectors;

/**
 * 表达式
 */
public abstract class Expression {
    // 左括号正则表达式匹配
    protected static final String LEFT_PARENTHESES_REGEX;

    // 右括号正则表达式匹配
    protected static final String RIGHT_PARENTHESES_REGEX;

    // 运算符号正则表达式匹配
    protected static final String CALCULATION_REGEX;

    static {
        // 从对应的元素中取出符号，并且组成正则表达式
        String leftRegex = LeftParentheses.LEFT.getSign().stream().map(str -> "\\" + str).collect(Collectors.joining());

        String rightRegex = RightParentheses.RIGHT.getSign()
                .stream()
                .map(str -> "\\" + str)
                .collect(Collectors.joining());

        String calculationRegex = Calculation.getCalculationValues()
                .stream()
                .map(Sign::getSign)
                .flatMap(Collection::stream)
                .map(str -> "\\" + str)
                .collect(Collectors.joining());

        LEFT_PARENTHESES_REGEX = StringFormatter.format("[{}]", leftRegex);
        RIGHT_PARENTHESES_REGEX = StringFormatter.format("[{}]", rightRegex);
        CALCULATION_REGEX = StringFormatter.format("[{}]", calculationRegex);
    }

    // 表达式的元素
    private List<ExpressionElement> elements;

    public Expression(List<ExpressionElement> elements) {
        this.elements = elements;
    }

    protected Expression() {

    }

    /**
     * 计算表达式
     */
    public BigDecimal calculation() {
        ElementNode rootNode = getRootNode();
        return calculation(rootNode);
    }

    private BigDecimal calculation(ElementNode rootNode) {
        // 如果该节点是子节点，则直接返回数字
        if (rootNode.isChild()) {
            return ((NumberElement) rootNode.getElement()).getNumber();
        }

        // 如果不是子节点，则返回左节点和右节点的运算结果
        BigDecimal left = calculation(rootNode.getLeftNode());
        BigDecimal right = calculation(rootNode.getRightNode());
        return calculationOne(left, (Calculation) rootNode.getElement(), right);
    }

    /**
     * 计算一对数字
     */
    protected BigDecimal calculationOne(BigDecimal leftNumber, Calculation calculation, BigDecimal rightNumber) {
        if (calculation == Calculation.PLUS) {
            return leftNumber.add(rightNumber);
        } else if (calculation == Calculation.MINUS) {
            return leftNumber.subtract(rightNumber);
        } else if (calculation == Calculation.MULTI) {
            return leftNumber.multiply(rightNumber);
        } else if (calculation == Calculation.DIVISION) {
            return leftNumber.divide(rightNumber, RoundingMode.HALF_UP);
        }

        throw new SignException(calculation.toString());
    }

    /**
     * 获得表达式根节点
     */
    public abstract ElementNode getRootNode();

    protected ElementNode getRootNode(List<ExpressionElement> expressionElements, boolean isPrefix) {
        List<ExpressionElement> elements = expressionElements;
        if (isPrefix) {
            elements = CollectionUtil.reverse(elements);
        }

        // 构造表达式树
        Stack<ElementNode> nodeStack = new Stack<>();
        for (ExpressionElement element : elements) {
            // 如果是数字，则直接入栈
            if (element instanceof NumberElement) {
                nodeStack.push(new ElementNode(element));
                continue;
            }

            // 如果不是数字，则连接两边的节点
            ElementNode rightNode = nodeStack.pop();
            ElementNode leftNode = nodeStack.pop();
            // 前缀表达式和后缀表达式的节点顺序不同
            if (isPrefix) {
                nodeStack.push(new ElementNode(element, rightNode, leftNode));
            } else {
                nodeStack.push(new ElementNode(element, leftNode, rightNode));
            }
        }
        return nodeStack.pop();
    }

    /**
     * 验证表达式是否合法，若不合法，则抛出异常
     */
    protected static void assertExpression(List<ExpressionElement> elements) {
        if (elements.stream().anyMatch(element -> element instanceof Parentheses)) {
            throw new PostfixPrefixExpressionException(elements);
        }

        // 数字元素的数量
        int numCount = 0;
        // 循环计数，为了抛出更加准确的异常信息
        int index = 0;
        for (ExpressionElement element : elements) {
            // 如果是数字元素，则增加计数
            if (element instanceof NumberElement) {
                numCount++;
                continue;
            }

            // 数字元素
            numCount -= 1;
            // 如果数字元素小于1，则抛出异常
            if (numCount < 1) {
                throw new PostfixPrefixExpressionException(elements, index);
            }

            // 循环计数加一
            index++;
        }
    }

    /**
     * 获取表达式的元素
     */
    public List<ExpressionElement> getElements() {
        return Collections.unmodifiableList(elements);
    }

    protected void setElements(List<ExpressionElement> elements) {
        this.elements = elements;
    }
}
